using System;
using System.Collections.Generic;
using System.Collections;
using System.Text;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Xml;
using System.IO;

using Verety.SharedMeta.MessagingServiceMeta.Contacts;
using Verety.SharedMeta.MessagingServiceMeta.Connection;

using Verety.SharedMeta.DirectoryServiceMeta.Conversation;
using Verety.SharedMeta.DirectoryServiceMeta.Conversation.ChatHistory;
using SBPweb.Modules.Modularity;

namespace SBPweb.Modules.ChatController.History
{
	public class ChatHistory
	{
		private IContactId ownerJID;
		private IContactId contactJID;
		private string routeAddress;
		private string filename;
		private IFormatter formatter;
		private IDirectoryConversationManager mConvManager;
		private IModule mParentModule;

		public const int DEFAULTMAXHISTORYENTRIES = 1000;

		public string FileName
		{
			get
			{
				return filename;
			}
		}

		public static string GetFileName(string path, string OwnerJIDBare, string ContactJIDBare, string routeAddress)
		{
			return System.IO.Path.Combine(path, string.Format("{0}_{1}_{2}.chat", OwnerJIDBare, ContactJIDBare, routeAddress));
		}

        public ChatHistory(IDirectoryConversationManager convManager, IContactId ownerJID, IContactId contactJID, string routeAddress, string path, IModule parentModule)
        {
			mParentModule = parentModule;
            mConvManager = convManager;
            filename = GetFileName(path, mConvManager.GetBareJId(ownerJID), mConvManager.GetBareJId(contactJID), routeAddress);
            formatter = mConvManager.CreateHistoryEntryFormatter();
            //			formatter = new HistoryEntryFormatter();

            if (!System.IO.Directory.Exists(path))
            {
                System.IO.Directory.CreateDirectory(path);
            }

            this.ownerJID = ownerJID;
            this.contactJID = contactJID;
            this.routeAddress = routeAddress;


            CropFile(DEFAULTMAXHISTORYENTRIES);
        }

		public void Finalize()
		{

		}

		public void AddHistoryEntry(IContactId to, IContactId from, PacketType packetType, IPacket packet)
		{
			DateTime timestamp = DateTime.Now;
			IHistoryEntry historyEntry = mConvManager.CreateHistoryEntryInstance(timestamp, to, from, packetType, packet);

			this.AddHistoryEntry(historyEntry);
		}

		public void AddHistoryEntry(IHistoryEntry historyEntry)
		{
			System.IO.FileStream stream;

			stream = new FileStream(this.filename, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite);
			
			Init(stream);
			
			try
			{
				formatter.Serialize(stream, historyEntry);
			}
			catch (Exception e)
			{
				if (mParentModule != null)
				{
					mParentModule.Host.LogManager.WriteInfo(this, "Cannot serialize the history stream. " + e.Message, "ChatController");
				}
			}
			finally
			{
				try
				{
					System.IO.StreamWriter writer = new System.IO.StreamWriter(stream);
					writer.Write("</history>");
					writer.Flush();
				}
				catch (Exception exc)
				{
					if (mParentModule != null)
					{
						mParentModule.Host.LogManager.WriteInfo(this, "Cannot write the history stream. " + exc.Message, "ChatController");
					}
				}

				stream.Flush();
				stream.Close();
			}
		}

		public string GetMessagesAsString(int count)
		{
			string retVal = string.Empty;
			ArrayList entries = ReadEntriesFromFile(count);

			foreach (IHistoryEntry he in entries)
			{
				retVal += he.FromUser + " : " + he.Message + "\n";
			}
			return retVal;
		}

		private ArrayList ReadEntriesFromFile(int count)
		{
			if (System.IO.File.Exists(filename))
			{
				System.IO.Stream stream;
				System.IO.MemoryStream memStream;
				System.IO.TextReader reader;
				System.IO.TextWriter writer;
				string line;

				ArrayList entryList = new ArrayList();

				stream = new System.IO.FileStream(filename, System.IO.FileMode.Open, System.IO.FileAccess.Read);
				memStream = new System.IO.MemoryStream();
				reader = new System.IO.StreamReader(stream);
				writer = new System.IO.StreamWriter(memStream);

				try
				{
					IHistoryEntry historyEntry;

					bool inNode = false;

					while ((line = reader.ReadLine()) != null)
					{
						if (!inNode)
						{
							if (line.StartsWith("</HistoryEntry>"))
							{
								return new ArrayList();
							}
							else if (line.StartsWith("<HistoryEntry>"))
							{
								inNode = true;
								writer.Write(line);
							}

							continue;
						}
						else
						{
							if (line.StartsWith("<HistoryEntry>"))
							{
								return new ArrayList();
							}
							else if (line.StartsWith("</HistoryEntry>"))
							{
								inNode = false;
								writer.Write(line);
								writer.Flush();
							}
							else
							{
								writer.Write(line);
								continue;
							}
						}

						memStream.Seek(0, System.IO.SeekOrigin.Begin);
						historyEntry = (IHistoryEntry)formatter.Deserialize(memStream);
						memStream.SetLength(0);

						if (historyEntry != null)
						{
							entryList.Add(historyEntry);
						}
						else
						{
							return new ArrayList();
						}

						if (entryList.Count > count)
						{
							entryList.RemoveAt(0);
						}
					}

					if (inNode)
						return new ArrayList();
					else
						return entryList;
				}
				catch (Exception)
				{
					return new ArrayList();
				}
				finally
				{
					reader.Close();
					writer.Close();
					memStream.Close();
					stream.Close();
				}
			}
			else
			{
				return new ArrayList();
			}
		}

		private void CropFile(int crop)
		{
			ArrayList entries = ReadEntriesFromFile(int.MaxValue);
			if (entries.Count > crop)
			{
				entries = entries.GetRange(entries.Count - crop - 1, crop);
				System.IO.FileStream stream;

				stream = new FileStream(this.filename, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite);

				Init(stream);

				try
				{
					foreach (IHistoryEntry he in entries)
						formatter.Serialize(stream, he);
				}
				catch (Exception e)
				{
					if (mParentModule != null)
					{
						mParentModule.Host.LogManager.WriteInfo(this, "Cannot serialize the history stream. " + e.Message, "ChatController");
					}
				}
				finally
				{
					try
					{
						System.IO.StreamWriter writer = new System.IO.StreamWriter(stream);
						writer.Write("</history>");
						writer.Flush();
					}
					catch (Exception exc)
					{
						if (mParentModule != null)
						{
							mParentModule.Host.LogManager.WriteInfo(this, "Cannot write the history stream. " + exc.Message, "ChatController");
						}
					}

					stream.Flush();
					stream.Close();
				}

			}
		}

		private void Init(FileStream fs)
		{

			fs.Seek(0, SeekOrigin.End);
			int i = 0;
			char chr = ' ';
			byte[] bytearray = new byte[1];
			// Find mathcing position
			do
			{
				--i;
				try
				{
					fs.Seek(i, SeekOrigin.End);
					bytearray[0] = (byte)fs.ReadByte();
					chr = Encoding.UTF8.GetChars(bytearray)[0];
				}
				catch (IOException)
				{
					// Corrupt logfile (since it is too short or full of with white spaces)
					fs.SetLength(0);
					break;
				}
			} while (Char.IsWhiteSpace(chr));

			if (!Char.IsWhiteSpace(chr))
			{
				bytearray = new byte[10];
				String str;
				try
				{
					fs.Seek(i - 9, SeekOrigin.End);
					fs.Read(bytearray, 0, 10);
					str = Encoding.UTF8.GetString(bytearray);
					if (str.ToLower().Equals("</history>"))
					{
						// The end of the file is closed - truncate the file before that tag
						fs.SetLength(fs.Length - 10);
					}
					else if (str.ToLower().Equals("</historyEntry>"))
					{
						fs.Seek(0, SeekOrigin.End); // Back to the end
					}
					else
					{
						// The end of the file is open - now we close the previous run
						fs.Seek(0, SeekOrigin.End);
						fs.Flush();
					}
				}
				catch (IOException)
				{
					// Corrupt logfile (since it is too short or full of with white spaces)
					fs.SetLength(0);
				}
			}
			

			// Temporary reference holder to lock on
			StreamWriter sw = new StreamWriter(fs);

			lock (sw)
			{
				if (fs.Length == 0)
				{
					// The file is new - write xml header
					sw.WriteLine("<?xml version=\"1.0\"?>");
					sw.WriteLine("<?xml-stylesheet type='text/xsl' href='History.xsl'?>");
					sw.WriteLine(string.Format("<history owner='{0}' contact='{1}' routeAddress='{2}'>", mConvManager.GetBareJId(this.ownerJID), this.contactJID.ToString(), this.routeAddress));
				}
				sw.Flush();
			}

		}

	}
}
